﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections;
using System.Reflection;

namespace Objects
{
    //public interface IIdentity
    //{
    //    Guid Id { get; }
    //}

    /// <summary>
    /// Супер-класс слоя домена
    /// </summary>
    [Class("Сущность")]
    public abstract partial class Entity : IUndoRedo, IChangingObservable, //IIdentity,
        ICloneable, IIndexAccess<string, Valuehost>
    {
        private Valuehost _Id;
        /// <summary>
        /// Идентификатор
        /// </summary>
        [Field("Идентификатор")]
        public Valuehost Id
        {
            get
            {
                return _Id;
            }
            set
            {
                if (!_Id.IsEquals(value))
                {
                    OnPropertyChanging("Id");
                    _Id = _Id.InjectExt(value);
                    OnPropertyChanged("Id");
                }
            }
        }

        internal class StatePoint : StatePoint<Entity>
        {
            public override Entity obj
            {
                protected set
                {
                    base.obj = value;
                    ((INotifyPropertyChanging)obj).PropertyChanging += new PropertyChangingEventHandler(obj_PropertyChanging);
                    ((INotifyPropertyChanged)obj).PropertyChanged += new PropertyChangedEventHandler(obj_PropertyChanged);
                }
            }

            private Stack<State> undo;
            private Stack<State> redo;

            private StatePoint(Entity stateObject)
                : base(stateObject)
            {
                Listening = true;
            }

            private State currState;

            private void Normalize()
            {
                redo.Clear();
            }

            private void obj_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (Listening)
                {
                    Normalize();
                    undo.Push(currState);
                    Refresh();
                }
            }

            protected override State ShotState(string propName)
            {
                State st;
                PropertyInfo pi;
                st.PropertyName = propName;
                st.ShowName = "Изменение {0}".goFormat(propName);
                st.IsContext = obj.ContextData.TryGetValue(propName, out st.Value);
                if (!st.IsContext)
                    if ((pi = TypeObj.GetProperty(propName)) != null)
                        st.Value = ((Valuehost)pi.GetValue(obj, nullArgs));
                st.Value = st.Value.CloneAs<Valuehost>();
                return st;
            }



            private void obj_PropertyChanging(object sender, PropertyChangingEventArgs e)
            {
                if (Listening)
                    currState = ShotState(e.PropertyName);
            }

            public static StatePoint Create(Entity stateObject)
            {
                if (stateObject != null)
                    return new StatePoint(stateObject);
                else
                    return null;
            }

            private bool Listening;

            /// <summary>
            /// Контракт: index >= 0 && index.LessThan(states.Count)
            /// </summary>
            /// <param name="index"></param>
            protected override void SetState(State state)
            {
                Listening = false;
                try
                {
                    if (state.IsContext)
                        obj.ContextData.AddOrSetValue(state.PropertyName, state.Value);
                    else
                    {
                        FieldInfo fi = TypeObj.GetField("_" + state.PropertyName, BindingFlags.NonPublic | BindingFlags.Instance);
                        if (fi != null)
                            fi.SetValue(obj, state.Value);
                        else
                        {
                            PropertyInfo pi = TypeObj.GetProperty(state.PropertyName);
                            pi.SetValue(obj, state.Value, nullArgs);
                        }
                    }
                }
                finally
                {
                    Listening = true;
                }
            }
            public override void Dispose()
            {
                obj.PropertyChanging -= this.obj_PropertyChanging;
                obj.PropertyChanged -= this.obj_PropertyChanged;
                base.Dispose();
            }
        }

        public IUndoRedoMan GetStatePoint()
        {
            return StatePoint.Create(this);
        }

        private ContextData _ContextData;
        public IDictionary<string, Valuehost> ContextData
        {
            get
            {
                if (_ContextData == null)
                    _ContextData = new ContextData();
                return _ContextData;
            }
        }

        /// <summary>
        /// Доступ к контекстной информации
        /// </summary>
        /// <param name="index">Индекс в формате Context.Property</param>
        /// <returns></returns>
        public Valuehost this[string index]
        {
            get { return ContextData.GetOrDefault(index, null); }
            set
            {
                Valuehost vh;
                if (ContextData.TryGetValue(index, out vh))
                {
                    if (!vh.IsEquals(value))
                    {
                        OnPropertyChanging(index);
                        ContextData[index] = vh.InjectExt(value);
                        OnPropertyChanged(index);
                    }
                }
                else
                {
                    ContextData.Add(index, value);
                    OnPropertyChanged(index);
                }
            }
        }

        public static implicit operator Valuehost(Entity value)
        {
            if (value != null)
                return value.InjectValue();
            else
                return null;
        }

        #region ICloneable Members
        private static object[] nullArgs = new object[0];
        public object Clone()
        {
            Type tp = this.GetType();
            Entity ent = (Entity)tp.GetConstructor(Type.EmptyTypes).Invoke(nullArgs);
            if (ent != null)
            {
                foreach (var item in tp.GetPropertiesWithAttribute<FieldAttribute>())
                    item.SetValue(ent, ((Valuehost)item.GetValue(this, null)).CloneAs<Valuehost>(), null);
                foreach (var item in ContextData)
                    ent.ContextData.Add(item.Key, item.Value.CloneAs<Valuehost>());
            }
            return ent;
        }

        #endregion

        #region Члены INotifyPropertyChanged

        public void OnPropertyChanged(string PropName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(PropName));
        }
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region INotifyPropertyChanging Members

        public void OnPropertyChanging(string propName)
        {
            if (PropertyChanging != null)
                PropertyChanging(this, new PropertyChangingEventArgs(propName));
        }

        public event PropertyChangingEventHandler PropertyChanging;

        #endregion

        #region IChangingObservable Members

        public class Observer : ChangingObserver
        {
            private class State : ChangingObserver.State
            {
                public bool IsCascade;
                public string PropertyName;
                public Valuehost Value;
                public bool IsContext;

                public void Setup(Observer obs)
                {
                    if (IsContext)
                        obs.obj.ContextData.AddOrSetValue(PropertyName, Value);
                    else
                    {
                        PropertyInfo pi = obs.TypeObj.GetProperty(PropertyName);
                        pi.SetValue(obs.obj, Value, nullArgs);
                    }
                    if (IsCascade)
                    {
                        IChangingObserver cobs = (IChangingObserver)obs.cascade[Value.Value];
                        cobs.Rollback();
                        if (!cobs.IsRollback)
                        {
                            obs.cascade.Remove(Value);
                            obs.Dispose();
                        }
                    }
                }
            }

            private Hashtable cascade;
            public Entity obj;

            private void Shot(string propName)
            {
                PropertyInfo pi;
                IChangingObservable co;
                object val;
                State st = new State() { PropertyName = propName };
                st.IsContext = obj.ContextData.TryGetValue(propName, out st.Value);
                if (!st.IsContext)
                    if ((pi = TypeObj.GetProperty(propName)) != null)
                        st.Value = ((Valuehost)pi.GetValue(obj, nullArgs));
                if (st.Value != null && (val = st.Value.Value) != null)
                {
                    IChangingObserver cobs = null;
                    if (!(st.IsCascade = cascade.ContainsKey(val)))
                    {
                        if (st.IsCascade = (co = val as IChangingObservable) != null && (cobs = co.GetObserver()) != null)
                        {
                            cascade.Add(co, cobs);
                            cobs.PropertyChanging += (sender, e) =>
                                ForceNotifyChanging(propName);
                        }
                    }
                }
                Shot(st);
            }

            private void obj_PropertyChanging(object sender, PropertyChangingEventArgs e)
            {
                if (Listening)
                {
                    Shot(e.PropertyName);
                    OnPropertyChanging(e.PropertyName);
                }
            }

            public static Observer Create(Entity stateObject)
            {
                if (stateObject != null)
                    return new Observer(stateObject);
                else
                    return null;
            }

            /// <summary>
            /// Контракт: index >= 0 && index.LessThan(states.Count)
            /// </summary>
            /// <param name="index"></param>
            protected override bool Stateup(ChangingObserver.State state)
            {
                if (!base.Stateup(state))
                {
                    State s = (State)state;
                    if (s.IsContext)
                        obj.ContextData.AddOrSetValue(s.PropertyName, s.Value);
                    else
                    {
                        PropertyInfo pi = TypeObj.GetProperty(s.PropertyName);
                        pi.SetValue(obj, s.Value, nullArgs);
                    }
                    if (s.IsCascade)
                    {
                        IChangingObserver cobs = (IChangingObserver)cascade[s.Value.Value];
                        cobs.Rollback();
                        if (!cobs.IsRollback)
                        {
                            cascade.Remove(s.Value);
                            Dispose();
                        }
                    }
                    return true;
                }
                else
                    return true;
            }

            #region INotifyPropertyChanging Members

            public override void ForceNotifyChanging(string propName)
            {
                obj_PropertyChanging(obj, new PropertyChangingEventArgs(propName));
            }

            #endregion

            #region IDisposable Members

            public override void Dispose()
            {
                obj.PropertyChanging -= this.obj_PropertyChanging;
                base.Dispose();
            }

            #endregion

            private Observer(Entity stateObject)
                : base(stateObject)
            {
                cascade = new Hashtable();
            }

            #region IChangingObserver Members

            public override object Object
            {
                get
                {
                    return obj;
                }
                protected set
                {
                    obj = value as Entity;
                    if (obj != null)
                    {
                        base.Object = value;
                        ((INotifyPropertyChanging)obj).PropertyChanging += new PropertyChangingEventHandler(obj_PropertyChanging);
                    }
                }
            }

            public override IChangingObserver GetFromCurrent()
            {
                return Observer.Create(obj);
            }

            #endregion
        }

        public IChangingObserver GetObserver()
        {
            return Observer.Create(this);
        }

        #endregion
    }

    public class ContextData : Dictionary<string, Valuehost>
    {
    }
}
